var chain = require("./lib/requestChain");
var cron = require('cron').CronJob;

const exitDelay = 2000;
const defaultMinReq = 500;
const defaultMaxReq = 1500;
const defaultTimeZone = 'Asia/Chongqing';
const defaultMonitorFreq = "0 * * * * *";
const defaultCrawlFreq = "*/30 * * * * *";

/**
 *  @description : control data flow and start crawling job.
 * */
var mainJob = function(crawler){
    crawler.count++;
    if(crawler.count % crawler.synGroup == 0) {
        crawler.group = true;
    }
    /* find last request */
    var cursor = crawler.requestChain;
    while(cursor.next) {
        cursor = cursor.next;
    }

    /* set final deal progress  */
    if(crawler.group) {
        /* synchronized analyzer */
        cursor.next = {filter: function(err,res,body){
            chain.cacheData(res);
            chain.flushData();
        }};
        crawler.group = false;
    } else {
        cursor.next = {filter: function(err,res,body){
            chain.cacheData(res);
        }};
    }

    /* start crawler progress */
    chain.startChain(crawler.requestChain);
};

/**
 *   Create Crawler by configuration or default configuration. Analyzer is alternative
 *   @param : config.name,config.requestChain,config.synGroup,config.minReq,config.maxReq,start,end,freq,monitor,timeZone
 *   @description : requestChain is required, means http chain linked list head node.Others have default value
 * */
var crawler = function(config,analyzer){
    var _self = this;

    this.name = config.name || "default";
    this.requestChain = config.requestChain || null;
    this.group = false;
    this.synGroup = config.synGroup || 1;
    this.minReq = config.minReq || defaultMinReq;
    this.maxReq = config.maxReq || defaultMaxReq;

    this.count = 0;
    this.start = config.start || new Date();
    this.end = config.end || null;
    this.freq = config.freq || defaultCrawlFreq;
    this.monitorFreq = config.monitor || defaultMonitorFreq;
    this.started = false;
    this.timeZone = config.timeZone || defaultTimeZone;

    this.dataBuilder = config.dataBuilder || function(cachedData,cookieArray,analyze){
        console.log("Crawler : dataBuilder not set,return raw data to analyzer");
        analyze.call(_self,cachedData);
    };
    this.job = new cron(this.freq,function(){
        mainJob(_self);
    },null,false,this.timeZone);

    this.monitor = new cron(this.monitorFreq,function(){
        if(new Date() >= _self.start){
            if(!_self.started) {
                _self.started = true;
                _self.job.start();
                console.log("Crawler : start crawler loop.");
            }
        }
        if(_self.end && new Date() > _self.end) {
            if(_self.started) {
                _self.started = false;
                _self.group = true;
                mainJob(_self);
                _self.job.stop();
                _self.monitor.stop();
                setTimeout(function(){console.log("Crawler : stop crawler loop.")},exitDelay);
            }
        }
    },null,true,this.timeZone);

    this.analyzer = analyzer || function(){
        console.log('Crawler : crawler process done,please set analyzer at first');
    };
    chain.injectScope(_self);
    console.log('Crawler : crawler initialized. CronTable->[' + this.freq+ ']');
    return _self;
};

/**
 *  @description : start crawler manually.start and end time will be ineffective.
 * */
crawler.prototype.manualStart = function() {
    this.monitor.stop();
    this.job.start();
    this.started = true;
    console.log("Crawler : manually start job cron...");
};

/**
 *  @description : stop crawler manually.start and end time will be ineffective.
 * */
crawler.prototype.manualStop = function() {
    if(this.started) {
        this.job.stop();
        console.log("Crawler : manually stop job cron...");
    } else {
        console.log("Crawler : please start job cron first.");
    }
};

/**
 *  @description : set analyzer.if use module 'analyzer',
 *  there will be a series of parallel executor,otherwise will be a single function
 * */
crawler.prototype.setAnalyzer = function(analyzer){
    if(typeof analyzer === 'function') {
        this.analyzer = analyzer;
    } else {
        console.log("please set function type variable");
    }
};

/**
 *  @description : if your function in filter contains node_modules,
 *  use DI to inject them, then use this.moduleName to invoke.
 * */
crawler.prototype.injectDependency = function(obj){
    chain.injectImports(obj);
};

exports.Crawler = crawler;